#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Skyboxes;
using System.Net;
using System.Net.Sockets;
using System.Text;
//using MySql.Data.MySqlClient;
#endregion

namespace Viking
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    class GameplayScreen : GameScreen
    {
        #region Fields

        ContentManager Content;
        //GraphicsDeviceManager graphics;

        private Random rand;
        SpriteBatch spriteBatch;
        SpriteFont font;
        private int score = 0;
        private static float TIME = 60;
        private Model weapon, player;
        private Matrix axeTransform;
        private string grunt_jump;
        private string grunt_attack;
        private Vector3 position; // Player's position
        private bool jumping;
        private float startY, t, jumpspeed = 0;
        private bool playedOnce = false;
        private List<Rock> rockList; // List of active rocks in the game
        private List<Rock> rocksToBeRemoved; // List of rocks to be removed on the next cycle
        private List<Hut> hutList;
        private List<Hut> hutsToBeRemoved;
        private float rockCooldown = 0.3f; // How long it will take for the rock throw to cool down
        private float rockTimer = 0.0f; // The timer for the rock throw's cooldown. Every time a rock is thrown,
        // this timer is reset to the rock's total cooldown time, and then counts down until it's less than zero.
        // When the rockTimer is no longer greater than zero, we can throw a rock.
        private float power = 0.0f; // Strength of a throw!
        private float timer;
        private bool timerstarted;

        float pauseAlpha;
        Effect effect;
        Vector3 viewVector;
        Skybox skybox;
        Matrix world = Matrix.Identity;
        Matrix view = Matrix.CreateLookAt(new Vector3(20, 0, 0), new Vector3(0, 0, 0), Vector3.UnitY);
        Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 600f, 0.1f, 100f);
        /*Matrix permWorld = Matrix.Identity;
        Matrix permView = Matrix.CreateLookAt(new Vector3(20, 0, 0), new Vector3(0, 0, 0), Vector3.UnitY);
        Matrix permProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 600f, 0.1f, 100f);*/
        Vector3 cameraPosition;
        string highScore;
        Texture2D texture, rockTexture;
        private float distance = 20; // The camera's distance from the player
        private float angle = 0; // The angle between the player's direction and the x-axis
        private Vector3 direction = new Vector3(-1f, 0f, 0f);

        #endregion

        #region Initialization


        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);
        }


        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            rand = new Random();
            // Create a new SpriteBatch, which can be used to draw textures.
            //spriteBatch = new SpriteBatch(GraphicsDevice);
            if (Content == null)
                Content = new ContentManager(ScreenManager.Game.Services, "Content");
            /*if (graphics == null)
                graphics = ScreenManager.Game.GraphicsDevice;*/
            // TODO: use this.Content to load your game content here
            font = Content.Load<SpriteFont>("gamefont");
            weapon = Content.Load<Model>("BeachBall");
            axeTransform = Matrix.CreateRotationX((float)Math.PI / 2.0f);
            player = Content.Load<Model>("BeachBall");
            position = new Vector3(0, 0, 0);
            skybox = new Skybox("Skyboxes/Islands", Content);
            effect = Content.Load<Effect>("Specular");            //NEW NEW
            texture = Content.Load<Texture2D>("BeachBallTexture");
            rockTexture = Content.Load<Texture2D>("rock");
            Sound.playSoundLoop("Sounds/forest", Content);
            grunt_attack = "Sounds/grunt2";
            grunt_jump = "Sounds/grunt1";
            jumping = false;
            jumpspeed = -20;
            t = 0;
            startY = position.Y;
            timerstarted = false;
            timer = TIME;
            rockList = new List<Rock>();
            rocksToBeRemoved = new List<Rock>();

            hutList = new List<Hut>();
            // Generate several random huts.
            for (int i = 0; i < 6; i++)
            {
                Hut temp = new Hut();
                temp.position = new Vector3((float)(40 * 2 * (rand.NextDouble() - 0.5)), 0f, (float)(40 * 2 * (rand.NextDouble() - 0.5)));
                hutList.Add(temp);
            }
            hutsToBeRemoved = new List<Hut>();
        }


        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            Content.Unload();
            
        }


        #endregion

        #region Update


        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {

            base.Update(gameTime, otherScreenHasFocus, false);

            // Gradually fade in or out depending on whether we are covered by the pause screen.
            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);


            if(timerstarted)
            {
                if (rockTimer > 0.0f) // Handle the rock throw's cooldown.
                {
                    rockTimer -= (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                foreach (Rock therock in rocksToBeRemoved) // Remove dead rocks.
                {
                    if (rockList.Contains(therock))
                        rockList.Remove(therock); // If it's dead, remove it
                }
                rocksToBeRemoved.Clear();
                foreach (Rock therock in rockList)
                {
                    therock.Update(gameTime); // update each one
                    if (therock.dead)
                        rocksToBeRemoved.Add(therock);
                    // We might put here some collision detection - check each rock to see if it intersects with each hut, and react accordingly
                    // that is, decrement the hut's health, kill the rock.
                }

                foreach (Hut theHut in hutsToBeRemoved)
                {
                    if (hutList.Contains(theHut))
                    {
                        hutList.Remove(theHut);
                        score++;
                    }
                }
                hutsToBeRemoved.Clear();
                foreach (Hut theHut in hutList)
                {
                    theHut.Update(gameTime);
                    if (theHut.dead)
                        hutsToBeRemoved.Add(theHut);
                }



                if (jumping)
                {
                    //GAME PHYSICS: y = y_o + v_o*t + 1/2*a*t^2
                    //normally, a = -g, but because the initial velocity v_o is negative due to the coordinate system, a = +g.
                    t += 0.075f;
                    position.Y = startY - jumpspeed * t - .5f * 9.8f * t * t;

                    if (position.Y <= startY)
                    {
                        position.Y = startY;
                        jumping = false;
                        t = 0;
                    }
                }
                timer -= .015f;
                if (timer <= 0)
                    timerstarted = false;
            }
        }
#endregion

        #region HandleInput
        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
            int playerIndex = (int)ControllingPlayer.Value;

            KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
            GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];

            // The game pauses either if the user presses the pause button, or if
            // they unplug the active gamepad. This requires us to keep track of
            // whether a gamepad was ever plugged in, because we don't want to pause
            // on PC if they are playing with a keyboard and have no gamepad at all!
            bool gamePadDisconnected = !gamePadState.IsConnected &&
                                       input.GamePadWasConnected[playerIndex];
            if(timerstarted)
            {
                if (input.IsPauseGame(ControllingPlayer) || gamePadDisconnected)
                {
                    ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
                }
                else
                {

                    // TODO: Add your update logic here
                    MouseState mouse = Mouse.GetState();
                    if (mouse.LeftButton == ButtonState.Pressed)
                        Sound.playSoundOnce("Sounds/death", Content);
                    KeyboardState state = Keyboard.GetState();
                    if (state.IsKeyDown(Keys.Left))
                        position += 0.3f * Vector3.Transform(direction, Matrix.CreateRotationY((float)Math.PI / 2));
                    if (state.IsKeyDown(Keys.Right))
                        position -= 0.3f * Vector3.Transform(direction, Matrix.CreateRotationY((float)Math.PI / 2));
                    if (state.IsKeyDown(Keys.Up))
                        position += 0.3f * direction;
                    if (state.IsKeyDown(Keys.Down))
                        position -= 0.3f * direction;
                    if (state.IsKeyDown(Keys.Q)) //change camera view angle
                    {
                        direction = Vector3.Transform(direction, Matrix.CreateRotationY(-0.02f));
                        angle += 0.02f;
                    }
                    if (state.IsKeyDown(Keys.E))
                    {
                        direction = Vector3.Transform(direction, Matrix.CreateRotationY(0.02f));
                        angle -= 0.02f;
                    }
                    if (power > 0)
                    {
                        if (!state.IsKeyDown(Keys.A) && rockTimer <= 0.0f)
                        {
                            Rock temp = new Rock(power, position, direction);
                            rockList.Add(temp);

                            Sound.playSoundOnce(grunt_attack, Content); //attack = A

                            rockTimer = rockCooldown;
                            power = 0;
                        }
                        else
                        {
                            power += 0.2f;
                        }
                    }
                    if (power == 0 && state.IsKeyDown(Keys.A))
                        power = 4;
                    // Put game physics in the update loop
                    if (state.IsKeyDown(Keys.Space) && !jumping)
                    {
                        Sound.playSoundOnce(grunt_jump, Content); //jump = spacebar
                        jumping = true;
                    }

                    //angle += 0.002f;
                    cameraPosition = position - distance * direction + new Vector3(0f, 4f, 0f);
                    view = Matrix.CreateLookAt(cameraPosition, position, Vector3.UnitY);
                    world = Matrix.CreateTranslation(position);
                }
            }
            if (!timerstarted)
            {
                KeyboardState state = Keyboard.GetState();
                if (state.IsKeyDown(Keys.Space))
                {
                    timerstarted = true;
                    timer = TIME;
                    playedOnce = true;
                    score = 0;
                }
                if (playedOnce && state.IsKeyDown(Keys.Q))
                {
                    playedOnce = false;
                    timerstarted = true;
                    timer = TIME;
                    Client.sendScore(score);
                    score = 0;
                }
            }
        }

#endregion

        #region Draw
        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // This game has a blue background. Why? Because!
            ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
                                               Color.CornflowerBlue, 0, 0);

            
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            spriteBatch.Begin();

            DrawModelWithEffect(player, Matrix.CreateTranslation(position), view, projection, texture); // This is the player's model.
            // The world matrix for it is translated by the position vector - indicating that it's drawn at [position].

            foreach (Rock theRock in rockList)
            {
                DrawModelWithEffect(weapon,
                    Matrix.CreateScale(0.5f) * Matrix.CreateTranslation(theRock.position), view, projection, rockTexture);
                // should draw each rock at its position, scaled down to half size.
            }
            foreach (Hut theHut in hutList)
            {
                DrawModelWithEffect(player,
                    Matrix.CreateScale(2f) * Matrix.CreateTranslation(theHut.position), view, projection, texture);
                // Huts are big.
            }

            //DrawModelWithEffect(model1, Matrix.Identity , view, projection); // We can't draw the bunny because it has no texture mapping.


            // Placeholders to mark the landscape a little bit (just so that we can get our bearings)
            DrawModelWithEffect(player,
                Matrix.CreateScale(0.2f) * Matrix.CreateTranslation(new Vector3(5.0f, 0.0f, 5.0f)), view, projection, texture);
            DrawModelWithEffect(player,
                Matrix.CreateScale(0.2f) * Matrix.CreateTranslation(new Vector3(5.0f, 0.0f, -5.0f)), view, projection, texture);
            DrawModelWithEffect(player,
                Matrix.CreateScale(0.2f) * Matrix.CreateTranslation(new Vector3(-5.0f, 0.0f, -5.0f)), view, projection, texture);
            DrawModelWithEffect(player,
                Matrix.CreateScale(0.2f) * Matrix.CreateTranslation(new Vector3(-5.0f, 0.0f, 5.0f)), view, projection, texture);

            spriteBatch.DrawString(font, "High Score: " + highScore, new Vector2(25, 10), Color.Black);
            spriteBatch.DrawString(font, "Score: " + score, new Vector2(350, 10), Color.Black);
            spriteBatch.DrawString(font, "Time: " + Math.Ceiling(timer), new Vector2(600, 10), Color.Black);

            RasterizerState rs = new RasterizerState();
            rs.CullMode = CullMode.CullClockwiseFace;
            ScreenManager.Game.GraphicsDevice.RasterizerState = rs;
            skybox.Draw(view, projection, cameraPosition);
            rs = new RasterizerState();
            rs.CullMode = CullMode.CullCounterClockwiseFace;
            ScreenManager.Game.GraphicsDevice.RasterizerState = rs;
            /*ScreenManager.Game.GraphicsDevice.RasterizerState.CullMode = CullMode.CullClockwiseFace;
            skybox.Draw(view, projection, cameraPosition);
            ScreenManager.Game.GraphicsDevice.RasterizerState.CullMode = CullMode.CullCounterClockwiseFace;*/


            if (!timerstarted)
            {
                if (!playedOnce)
                    spriteBatch.DrawString(font, "Press space to start timer", new Vector2(250, 250), Color.Black);
                else
                    spriteBatch.DrawString(font, "Press space to reset timer\nPress q to send score", new Vector2(250, 250), Color.Black);
            }
            spriteBatch.End();
            base.Draw(gameTime);
        }

        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.World = world;
                    effect.View = view;
                    effect.Projection = projection;
                    effect.EnableDefaultLighting();
                }

                mesh.Draw();
            }
        }

        private void DrawModelWithEffect(Model model, Matrix world, Matrix view, Matrix projection, Texture2D texture)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = effect;
                    effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
                    effect.Parameters["View"].SetValue(view);
                    effect.Parameters["Projection"].SetValue(projection);
                    Matrix worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
                    effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);
                    effect.Parameters["ViewVector"].SetValue(viewVector);

                    effect.Parameters["AmbientColor"].SetValue(Color.Green.ToVector4());
                    effect.Parameters["AmbientIntensity"].SetValue(0.5f);
                    effect.Parameters["ModelTexture"].SetValue(texture);
                    
                    //effect.Parameters["CameraPosition"].SetValue(cameraPosition);
                }
                mesh.Draw();
            }
        }
        #endregion
    }
}
